iT邦幫忙

2024 iThome 鐵人賽

DAY 18
2
Kubernetes

都什麼年代了,還在學 Kubernetes系列 第 18

學 Kubernetes 的第十八天 - Workloads - StatefulSet

  • 分享至 

  • xImage
  •  

在 Kubernetes 中,StatefulSet 是一種專門用來管理有狀態應用的工作負載控制器。與無狀態應用不同,有狀態應用通常需要每個 Pod 有穩定的網絡標識符(如 DNS 名稱)和持久性存儲。StatefulSet 保證了 Pod 的穩定標識、穩定存儲和有序部署、擴展以及刪除。

為什麼需要 StatefulSet

StatefulSet 的設計目的是解決有狀態應用中的一些常見需求,如:

  1. 穩定的網絡標識:每個 Pod 都有一個穩定的 DNS 名稱,這對於集群中的節點之間的點對點通信很重要。
  2. 穩定的存儲:每個 Pod 都可以擁有自己專屬的 PersistentVolume,這些卷與 Pod 的生命周期分離,保證了即使 Pod 重新調度,數據也能保留。
  3. 有序性:StatefulSet 保證 Pod 按照指定的順序啟動、關閉和刪除,這在需要按順序操作的應用場景中非常關鍵。

StatefulSet 與 Deployment 的差異

  • 網絡標識:StatefulSet 為每個 Pod 分配一個穩定的名稱(如 podname-0, podname-1),而 Deployment 中的 Pod 是無狀態的,每個 Pod 的名稱和 IP 可能會變化。
  • 存儲:StatefulSet 為每個 Pod 分配獨立的 PersistentVolume,這些存儲是持久且與 Pod 綁定的。Deployment 則沒有這樣的機制,Pod 重啟後的存儲是臨時的。
  • 有序性:StatefulSet 保證 Pod 按順序啟動、關閉和更新,而 Deployment 中 Pod 的啟動和關閉順序是不受控制的。

應用場景

  1. 分佈式數據庫:如 Cassandra、MongoDB,需要每個節點(Pod)有穩定的標識和存儲,並按順序啟動。
  2. 有狀態應用:如 Zookeeper、Kafka 等,需要嚴格的有序操作和穩定的存儲。
  3. 儲存依賴的應用:需要確保每個應用節點能夠持續訪問相同的存儲。

組態檔案說明

以下是一個簡單的 StatefulSet 組態檔範例:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
  • serviceName: "nginx":指定 StatefulSet 所屬的 Headless Service,用於管理 Pod 的 DNS 名稱和連接。
  • replicas: 3:定義 3 個 Pod 副本,這些 Pod 是有狀態的,且每個 Pod 都會獲得一個唯一的名稱和持久存儲。
  • volumeClaimTemplates:每個 Pod 都會創建一個名為 www 的 PersistentVolumeClaim,並請求 1Gi 的存儲空間。
  • 每個 Pod 都將一個 PersistentVolume 掛載到 /usr/share/nginx/html,確保每個副本的數據是隔離的。

實作

接下來,我們將使用以下組態檔案來建立一個 StatefulSet 及其所依賴的 Service。該配置將建立一個無頭服務(Headless Service)nginx,以便發布 StatefulSet web 中各個 Pod 的 IP 地址。

組態檔案: sts.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

這個組態檔案包含:

  • 名為 nginx 的 headless service
  • 名為 web 的 statefulset

順序建立 Pod

  • 打開一個新終端 t1 ,使用 --watch flag 監控 Pod 的變化
kubectl get pods --watch -l app=nginx
  • 建立資源
kubectl apply -f sts.yaml
  • 回到 t1 終端,觀察 Pod 的變化
NAME    READY   STATUS    RESTARTS   AGE
web-0   0/1     Pending   0          0s
web-0   0/1     Pending   0          4s
web-0   0/1     ContainerCreating   0          4s
web-0   1/1     Running             0          14s
web-1   0/1     Pending             0          0s
web-1   0/1     Pending             0          5s
web-1   0/1     ContainerCreating   0          5s
web-1   1/1     Running             0          14s
web-2   0/1     Pending             0          0s
web-2   0/1     Pending             0          4s
web-2   0/1     ContainerCreating   0          4s
web-2   1/1     Running             0          5s

可以看到,3 個 Pod 是依序建立的,等到前一個 Pod 狀態變成 Running 後,才會開始建立下一個 Pod,直到全部 Pod 完成。

使用穩定的網路身份標識

  • 查詢屬於 nginx StatefulSet 的 Pod
kubectl get pods -l app=nginx
---
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          5m17s
web-1   1/1     Running   0          5m3s
web-2   1/1     Running   0          4m49s
  • 迭帶查詢三個 Pod 的 hostname
for i in 0 1 2; do kubectl exec "web-$i" -- sh -c 'hostname'; done
---
web-0
web-1
web-2

可以看到,StatefulSet 建立的 Pod 使用的是累進的數字當作 hostname,這也讓 Pod 變得更好預測。

  • 創建並進入測試用 Pod
kubectl run -it --image busybox:1.36 dns-test --restart=Never --rm

之前的章節我們有提到,Headless Service 綁定 StatefulSet 的 Pod label,每個符合 selector 的 Pod 都會獲得一個 DNS 名稱。格式為 <pod-name>.<service-name>.<namespace>.svc.cluster.local

  • 查詢上面 3 個 Pod 的 DNS 名稱
nslookup web-0.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.4 web-0.nginx.default.svc.cluster.local
----------
nslookup web-1.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.4 web-1.nginx.default.svc.cluster.local
----------
nslookup web-2.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-2.nginx
Address 1: 10.244.2.6 web-2.nginx.default.svc.cluster.local

可以看到,我們的確可以透過 DNS 名稱分別訪問每個 Pod。

接下來,我們來驗證 IP 變動的影響。

  • 刪除 StatefulSet 所有的 Pod
kubectl delete pod -l app=nginx
---
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted

這樣會讓 Pod 重新建立,它們會被叢集分配新的 IP。

  • 重新創建並進入測試用 Pod
kubectl run -it --image busybox:1.36 dns-test --restart=Never --rm
  • 查詢上面 3 個 Pod 的 DNS 名稱
nslookup web-0.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.5 web-0.nginx.default.svc.cluster.local
----------
nslookup web-1.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.8 web-1.nginx.default.svc.cluster.local
----------
nslookup web-2.nginx
---
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-2.nginx
Address 1: 10.244.2.9 web-2.nginx.default.svc.cluster.local

可以看到 pod 對應的 IP 已經改變了,但我們依然可以使用相同的 DNS 名稱訪問對應的 Pod。

這就是為什麼不要在其他應用中使用 StatefulSet 中特定 Pod 的 IP 地址進行連接,因為 Pod 一旦重啟 IP 就會改變。

寫入穩定的儲存

StatefulSet 控製器建立了三個 PersistentVolumeClaims, 繫結到三個 PersistentVolumes。我們來驗證一下。

  • 查詢 StatefulSet 建立的 PersistentVolumeClaims
kubectl get pvc -l app=nginx
---
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
www-web-0   Bound    pvc-1452ead9-51d4-4eb6-ac71-62a27b87572c   1Gi        RWO            standard       <unset>                 67m
www-web-1   Bound    pvc-ee106200-53cd-40cc-8cfb-b489eff8a259   1Gi        RWO            standard       <unset>                 67m
www-web-2   Bound    pvc-9d9cd83c-d4e9-48ef-8352-ebf5d3317ee1   1Gi        RWO            standard       <unset>                 66m

NginX Web 伺服器默認會載入位於 /usr/share/nginx/html/index.html 的 index 檔案。 StatefulSet spec 中的 volumeMounts 欄位保證了 /usr/share/nginx/html 資料夾由一個 PersistentVolume 卷支援。

  • 將 Pod 的主機名寫入它們的 index.html 檔案並
for i in 0 1 2; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done
  • 在所有容器中訪問 nginx web service,取得 hostname
for i in 0 1 2; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
---
web-0
web-1
web-2
  • 刪除 StatefulSet 所有的 Pod
kubectl delete pod -l app=nginx
---
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted
  • 等待 pod 重新建立後,在所有容器中訪問 nginx web service,取得 hostname
for i in 0 1 2; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
---
web-0
web-1
web-2

可以看到,即使 Pod 被重新建立,StatefulSet 依然會自動為新的 Pod 繫結對應的 PV。

擴容/縮容/順序終止 Pod

我們可以將 StatefulSet 視為 ReplicaSet 的一個高級變體,因此他也有 ReplicaSet 的擴容/縮容功能。

  • 回到 t1 終端,重新使用 --watch flag 監控 Pod 的變化
kubectl get pods --watch -l app=nginx
  • web 擴展到 5
kubectl scale sts web --replicas=5
# 或者使用指令:
kubectl patch sts web -p '{"spec":{"replicas":5}}'
  • web 縮回 2
kubectl scale sts web --replicas=2
# 或者使用指令:
kubectl patch sts web -p '{"spec":{"replicas":2}}'
  • 回到 t1 終端,觀察 Pod 的變動情況
NAME    READY   STATUS              RESTARTS   AGE
web-3   0/1     Pending             0          0s
web-3   0/1     Pending             0          0s
web-3   0/1     ContainerCreating   0          0
web-3   1/1     Running             0          1s
web-4   0/1     Pending             0          0s
web-4   0/1     Pending             0          0s
web-4   0/1     ContainerCreating   0          0s
web-4   1/1     Running             0          1s

web-4   1/1     Terminating         0          28s
web-4   0/1     Terminating         0          28s
web-4   0/1     Terminating         0          28s
web-4   0/1     Terminating         0          28s
web-4   0/1     Terminating         0          28s
web-3   1/1     Terminating         0          29s
web-3   0/1     Terminating         0          30s
web-3   0/1     Terminating         0          30s
web-3   0/1     Terminating         0          30s
web-3   0/1     Terminating         0          30s
web-2   1/1     Terminating         0          44s
web-2   0/1     Terminating         0          44s
web-2   0/1     Terminating         0          45s
web-2   0/1     Terminating         0          45s
web-2   0/1     Terminating         0          45s

由上面可以觀察到:

  • 擴容時,會正序建立 Pod
  • 縮容時,會倒序終止 Pod

另外,如果你重新獲取 StatefulSet 的 PersistentVolumeClaims,會發現擴展到 5 個副本時,掛載到 StatefulSet Pod 的 PersistentVolume 不會被刪除。這是為了讓使用者有機會就回 Volume 中的資料。

  • 查詢 PVC
kubectl get pvc -l app=nginx
---
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
www-web-0   Bound    pvc-1452ead9-51d4-4eb6-ac71-62a27b87572c   1Gi        RWO            standard       <unset>                 171m
www-web-1   Bound    pvc-ee106200-53cd-40cc-8cfb-b489eff8a259   1Gi        RWO            standard       <unset>                 170m
www-web-2   Bound    pvc-9d9cd83c-d4e9-48ef-8352-ebf5d3317ee1   1Gi        RWO            standard       <unset>                 170m
www-web-3   Bound    pvc-192ba651-004b-4ef3-af6c-e95adea1a288   1Gi        RWO            standard       <unset>                 53m
www-web-4   Bound    pvc-656565e5-d3a5-4114-804e-ca14dd0c31ac   1Gi        RWO            standard       <unset>                 53m

參考


上一篇
學 Kubernetes 的第十七天 - Storage - PV & PVC
下一篇
學 Kubernetes 的第十九天 - Workloads - Job & CronJob
系列文
都什麼年代了,還在學 Kubernetes37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言